﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Diagnostics;
using System.Windows.Media.Imaging;
using System.Windows.Threading;


namespace HeritageAcademy.Library.Controls
{
    public class AnimatedScrollDecorator : Decorator
    {
		#region Fields 

        double _downOffset = 0;
        private double _DragThreshold = 5.0;
        bool _isDragging = false;
        private bool _IsDraggingEnabled = true;
        bool _isInitialLayout = true;
        private Rect _lastChildArrange;
        Point? _lastDown = null;
        DispatcherTimer firstLayoutTimer;
        DoubleAnimationUsingKeyFrames prevAnim = null;

		#endregion Fields 

		#region Properties 

        public double DragThreshold
        {
            get { return _DragThreshold; }
            set { _DragThreshold = value; }
        }

        public bool IsDraggingEnabled
        {
            get { return _IsDraggingEnabled; }
            set { _IsDraggingEnabled = value; }
        }

		#endregion Properties 

		#region Event Handlers 

        void prevAnim_Completed(object sender, EventArgs e)
        {
            RaiseAnimationCompleted();
            prevAnim.Completed -= new EventHandler(prevAnim_Completed);
            prevAnim = null;
        }

		#endregion Event Handlers 

		#region Protected Methods 

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (this.Child == null) return base.ArrangeOverride(finalSize);

            if (_isInitialLayout && firstLayoutTimer == null)
            {
                firstLayoutTimer = new DispatcherTimer();
                firstLayoutTimer.Interval = TimeSpan.FromMilliseconds(500);

                EventHandler handler = null;
                handler = delegate(object s, EventArgs e)
                {
                    firstLayoutTimer.Tick -= handler;
                    firstLayoutTimer.Stop();
                    _isInitialLayout = false;
                };

                firstLayoutTimer.Tick += handler;
                firstLayoutTimer.Start();
            }

            double left = 0;
            double top = 0;
            double width = Child.DesiredSize.Width;
            double height = Child.DesiredSize.Height;

            // calculate left
            if (width <= finalSize.Width)
            {
                // center the content
                double offset = (finalSize.Width - width) / 2;
                left = offset;
            }
            else
            {
                // use the relative offset property to determine the placement of the content

                double delta = width - finalSize.Width;
                double offset = (this.IsReversed ? this.RelativeHorizontalOffset : (1 - this.RelativeHorizontalOffset)) * (delta * -1);
                left = offset;
            }

            left = Math.Round(left);

            if (this.IsAnimationEnabled && !_isInitialLayout)
            {
                // Leaving left as 0 to emphasize that the value for left will be provided when we
                // animate the ArrangeChildLeft property.

                _lastChildArrange = new Rect(0, top, width, height);
                StartAnimation(left);
            }
            else
            {
                this.ArrangeChildLeft = left;
                this.Child.Arrange(new Rect(left, top, width, height));
            }

            return finalSize;

        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if (this.Child == null) base.MeasureOverride(availableSize);

            // measure the item at whatever site it wants
            Child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

            // return a size that is the smaller of availableSize and the childs DesiredSize
            return new Size(Math.Min(Child.DesiredSize.Width, availableSize.Width), Math.Min(Child.DesiredSize.Height, availableSize.Height));
        }

        protected override void OnPreviewMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!IsDraggingEnabled)
            {
                base.OnMouseDown(e);
                return;
            }

            if (this.ActualWidth > this.Child.DesiredSize.Width)
            {
                return;
            }

            _lastDown = e.GetPosition(this);
            _downOffset = this.RelativeHorizontalOffset;

            base.OnMouseDown(e);
        }

        protected override void OnPreviewMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            if (!IsDraggingEnabled)
            {
                base.OnMouseMove(e);
                return;
            }

            if (!_isDragging && (this.ActualWidth > this.Child.DesiredSize.Width))
            {
                return;
            }

            if (_lastDown != null)
            {
                Point m = _lastDown.Value;
                Point p = e.GetPosition(this);

                if (_isDragging || Math.Abs(m.X - p.X) >= DragThreshold)
                {
                    _isDragging = true;
                    this.CaptureMouse();

                    double relOffset = (m.X - p.X) / 1500;//Math.Min(this.ActualWidth, 1000);
                    this.RelativeHorizontalOffset = _downOffset - relOffset;

                    if (this.EatMouseEventsOnDrag) e.Handled = true;
                }
            }
            else
            {
                base.OnMouseMove(e);
            }
        }

        protected override void OnPreviewMouseUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!IsDraggingEnabled)
            {
                base.OnMouseUp(e);
                return;
            }

            _lastDown = null;

            if (_isDragging)
            {
                _isDragging = false;
                if (this.EatMouseEventsOnDrag) e.Handled = true;
                this.ReleaseMouseCapture();
            }
            else
            {
                base.OnMouseUp(e);
            }
        }

        #region EatMouseEventsOnDrag

        /// <summary>
        /// EatMouseEventsOnDrag Dependency Property
        /// </summary>
        public static readonly DependencyProperty EatMouseEventsOnDragProperty =
            DependencyProperty.Register("EatMouseEventsOnDrag", typeof(bool), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((bool)true));

        /// <summary>
        /// Gets or sets the EatMouseEventsOnDrag property.  This dependency property 
        /// indicates ....
        /// </summary>
        public bool EatMouseEventsOnDrag
        {
            get { return (bool)GetValue(EatMouseEventsOnDragProperty); }
            set { SetValue(EatMouseEventsOnDragProperty, value); }
        }

        #endregion



        protected override void OnMouseWheel(System.Windows.Input.MouseWheelEventArgs e)
        {
            double relPixel = 0.05 / this.ActualWidth;
            this.RelativeHorizontalOffset += (relPixel * e.Delta);
        }

		#endregion Protected Methods 

		#region Private Methods 

        private void StartAnimation(double to)
        {
            DoubleAnimationUsingKeyFrames offsetAnimation = new DoubleAnimationUsingKeyFrames();

            if (prevAnim == null)
            {
                RaiseAnimationStarted();
            }
            else
            {
                prevAnim.Completed -= new EventHandler(prevAnim_Completed);
            }

            prevAnim = offsetAnimation;
            prevAnim.Completed += new EventHandler(prevAnim_Completed);

            offsetAnimation.KeyFrames.Clear();

            SplineDoubleKeyFrame k = new SplineDoubleKeyFrame(to, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.Duration)));
            k.KeySpline = this.Easing;
            offsetAnimation.KeyFrames.Add(k);

            this.BeginAnimation(ArrangeChildLeftProperty, offsetAnimation);
        }

		#endregion Private Methods 

        #region Duration

        /// <summary>
        /// Duration Dependency Property
        /// </summary>
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(double), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((double)1000));

        /// <summary>
        /// Gets or sets the Duration property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double Duration
        {
            get { return (double)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        #endregion

        #region Easing

        /// <summary>
        /// Easing Dependency Property
        /// </summary>
        public static readonly DependencyProperty EasingProperty =
            DependencyProperty.Register("Easing", typeof(KeySpline), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((KeySpline)new KeySpline(0, 0.25, 0, 1)));

        /// <summary>
        /// Gets or sets the Easing property.  This dependency property 
        /// indicates ....
        /// </summary>
        public KeySpline Easing
        {
            get { return (KeySpline)GetValue(EasingProperty); }
            set { SetValue(EasingProperty, value); }
        }

        #endregion

        #region ArrangeChildLeft (Private DependencyProperty / Treat as Write-only)

        /// <summary>
        /// ArrangeChildLeft Dependency Property
        /// </summary>
        private static readonly DependencyProperty ArrangeChildLeftProperty =
            DependencyProperty.Register("ArrangeChildLeft", typeof(double), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((double)0.0,
                    new PropertyChangedCallback(OnArrangeChildLeftChanged)));

        /// <summary>
        /// Gets or sets the ArrangeChildLeft property.  This dependency property 
        /// indicates ....
        /// </summary>
        private double ArrangeChildLeft
        {
            get { return (double)GetValue(ArrangeChildLeftProperty); }
            set { SetValue(ArrangeChildLeftProperty, value); }
        }

        /// <summary>
        /// Handles changes to the ArrangeChildLeft property.
        /// </summary>
        private static void OnArrangeChildLeftChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AnimatedScrollDecorator)d).OnArrangeChildLeftChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ArrangeChildLeft property.
        /// </summary>
        private void OnArrangeChildLeftChanged(DependencyPropertyChangedEventArgs e)
        {
            _lastChildArrange.X = (double)e.NewValue;
            this.Child.Arrange(_lastChildArrange);
        }

        #endregion

        #region RelativeHorizontalOffset

        /// <summary>
        /// RelativeHorizontalOffset Dependency Property
        /// </summary>
        public static readonly DependencyProperty RelativeHorizontalOffsetProperty =
            DependencyProperty.Register("RelativeHorizontalOffset", typeof(double), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((double)0.5,
                    FrameworkPropertyMetadataOptions.AffectsArrange,
                    new PropertyChangedCallback(OnRelativeHorizontalOffsetChanged),
                    new CoerceValueCallback(CoerceRelativeHorizontalOffsetValue)));

        /// <summary>
        /// Gets or sets the RelativeHorizontalOffset property.  This dependency property 
        /// indicates ....
        /// </summary>
        public double RelativeHorizontalOffset
        {
            get { return (double)GetValue(RelativeHorizontalOffsetProperty); }
            set { SetValue(RelativeHorizontalOffsetProperty, value); }
        }

        /// <summary>
        /// Handles changes to the RelativeHorizontalOffset property.
        /// </summary>
        private static void OnRelativeHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AnimatedScrollDecorator)d).OnRelativeHorizontalOffsetChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the RelativeHorizontalOffset property.
        /// </summary>
        protected virtual void OnRelativeHorizontalOffsetChanged(DependencyPropertyChangedEventArgs e)
        {

        }

        /// <summary>
        /// Coerces the RelativeHorizontalOffset value.
        /// </summary>
        private static object CoerceRelativeHorizontalOffsetValue(DependencyObject d, object value)
        {
            return Math.Min(1.0, Math.Max(0.0, (double)value));
        }

        #endregion

        #region IsReversed

        /// <summary>
        /// IsReversed Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsReversedProperty =
            DependencyProperty.Register("IsReversed", typeof(bool), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((bool)false,
                    FrameworkPropertyMetadataOptions.AffectsArrange));

        /// <summary>
        /// Gets or sets the IsReversed property.  This dependency property 
        /// indicates ....
        /// </summary>
        public bool IsReversed
        {
            get { return (bool)GetValue(IsReversedProperty); }
            set { SetValue(IsReversedProperty, value); }
        }

        #endregion

        #region IsAnimationEnabled

        /// <summary>
        /// IsAnimationEnabled Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsAnimationEnabledProperty =
            DependencyProperty.Register("IsAnimationEnabled", typeof(bool), typeof(AnimatedScrollDecorator),
                new FrameworkPropertyMetadata((bool)true));

        /// <summary>
        /// Gets or sets the IsAnimationEnabled property.  This dependency property 
        /// indicates ....
        /// </summary>
        public bool IsAnimationEnabled
        {
            get { return (bool)GetValue(IsAnimationEnabledProperty); }
            set { SetValue(IsAnimationEnabledProperty, value); }
        }

        #endregion

        #region AnimationStarted

        public event EventHandler AnimationStarted;

        private void RaiseAnimationStarted()
        {
            if (AnimationStarted != null)
            {
                AnimationStarted(this, new EventArgs());
            }
        }

        #endregion

        #region AnimationCompleted

        public event EventHandler AnimationCompleted;

        private void RaiseAnimationCompleted()
        {
            if (AnimationCompleted != null)
            {
                AnimationCompleted(this, new EventArgs());
            }
        }

        #endregion
    }
}
